<?php
/**
 * Defines base language class.
 *
 * @copyright &copy; 2011  Oleg Sychev
 * @author Oleg Sychev, Sergey Pashaev, Volgograd State Technical University
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 * @package blocks
 */

global $CFG;
require_once($CFG->dirroot . '/blocks/formal_langs/tokens_base.php');
require_once($CFG->dirroot . '/blocks/formal_langs/scaners.php');
require_once($CFG->dirroot . '/blocks/formal_langs/parsers.php');

abstract class block_formal_langs_abstract_language {

    /**
     * Id in language table.
     * @var integer
     */
    private $id;

    /**
     * Language version (for user-defined languages mainly).
     * @var integer
     */
    private $version;

    /**
     * Flag indicates that language has parser.
     * @var boolean
     */
    private $couldparse;

    /**
     * Returns true if this language has predefined (hardcoded) lexer and parser.
     *
     * @return boolean
     */
    abstract public function is_predefined();

    /**
     * Returns technical name of the language.
     */
    abstract public function name();

    /**
     * User-visible name of the language
     *
     * The rules for it are different for predefined and user-defined languages
     */
    abstract public function ui_name();

    /**
     * User description (help) for the language
     */
    abstract public function description();

    /**
     * Returns true if this language has parser enabled.
     *
     * @return boolean
     */
    public function could_parse() {
        return $this->couldparse;
    }

    /**
     * Fills token stream field of the processed string objects
     *
     * Add lexical errors if any exists.
     * @param object block_formal_langs_processed_string object with string filled
     *
     */
    abstract public function tokenize($processedstring);
    
    /**
     * Fills syntax tree field of the processed string objects.
     *
     * Add errors for answer parsing
     * @param $processedstring - block_formal_langs_processed_string object with string filled
     * @param $iscorrect boolean true if we need to reduce to start symbol (correct text parsing), false if not (compared text parts parsing)
     * @return array of objects of ast (or tree roots) - should contain one element for answer parsing
     */
    abstract public function parse($processedstring, $iscorrect);
}

/**
 * Predefined language class.
 *
 * @copyright  &copy; 2011 Sergey Pashaev, Volgograd State Technical University
 * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
 */
abstract class block_formal_langs_predefined_language extends block_formal_langs_abstract_language {

    public function __construct($langdbrecord) {
        $this->id = $langdbrecord->id;
        $this->version = $langdbrecord->version;
        $this->couldparse = $langdbrecord->parser_enabled;
    }

    /**
     * Returns true if this language has predefined (hardcoded) lexer and parser.
     *
     * @return boolean
     */
    public function is_predefined() {
        return true;
    }

    /**
     * User-visible name of the language
     */
    public function ui_name() {
        return get_string('lang_' . $this->name() , 'block_formal_langs');
    }

    /**
     * User description (help) for the language
     */
    public function description() {
        return get_string('lang_' . $this->name() . '_help' , 'block_formal_langs');
    }

    public function tokenize($processedstring) {
        global $CFG;
        require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/predefined/' . $this->name() . '_lexer.php');
        $classname = $this->name() . '_lexer';
        $lexer = new $classname(fopen('data://text/plain;base64,' . base64_encode($processedstring->string), 'r'));
        $tokens = array();
        while ($token = $lexer->next_token()) {
            $tokens[] = $token;
        }
        $processedstring->tokenstream = new block_formal_langs_token_stream();
        $processedstring->tokenstream->tokens = $tokens;
        return $processedstring;
    }

    public function parse($processedstring, $iscorrect) {
        global $CFG;
        require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/predefined/' . $this->name() . '_parser.php');
        $classname = $this->name() . '_yyParser';
        $parser = new $classname();
        $tokens = $processedstring->tokenstream->tokens;
        foreach($tokens as $token) {
            $parser->process_token($token);
        }
        $parser->doParse(0);
        if ($parser->success) {
            return $parser->ast;
        } else {
            /* Error */
            return null;
        }
    }
}


/**
 * Customized language class.
 *
 * @copyright  &copy; 2011 Sergey Pashaev, Volgograd State Technical University
 * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
 */
class block_formal_langs_userdefined_language extends block_formal_langs_abstract_language {

    /**
     * Technical name. 
     *
     * @var string
     */
    private $name;

    /**
     * User-visible name. 
     *
     * @var string
     */
    private $uiname;

    /**
     * Description string.
     * @var string
     */
    private $description;

    /**
     * Lexer rules.
     * @var string
     */
    private $lexerrules;

    /**
     * Parser rules.
     * @var string
     */
    private $parserrules;

    public function __construct($langdbrecord) {
        $this->id = $langdbrecord->id;
        $this->version = $langdbrecord->version;
        $this->couldparse = $langdbrecord->parser_enabled;
        $this->name = $langdbrecord->tech_name;
        $this->uiname = $langdbrecord->name;
        $this->lexerrules = $langdbrecord->lexer;
        $this->parserrules = $langdbrecord->parser;
    }

    public function tokenize($processedstring) {
        global $CFG;
        require_once($CFG->dirroot . '/blocks/formal_langs/scaners.php');
        
        $common_lexer = new block_formal_langs_scaner($this->lexerrules);

        $result = $common_lexer->tokenize($processedstring->string, false);
        return $result;
    }

    public function parse($processedstring, $iscorrect) {
        global $CFG;
        require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/system/grammar_pattern_lexer.php');
        require_once($CFG->dirroot . '/blocks/formal_langs/langs_code/system/grammar_pattern_parser.php');
        require_once($CFG->dirroot . '/blocks/formal_langs/grammar_pattern.php');

        require_once($CFG->dirroot . '/blocks/formal_langs/parsers.php');
        
        $grm_lexer = new grammar_pattern_lexer(fopen('data://text/plain;base64,' . base64_encode($this->parserrules), 'r'));
        $grm_parser = new grammar_pattern_yyParser();
        while ($token = $grm_lexer->next_token()) {
            $grm_parser->doParse($token->type(), $token->value());
        }
        $grm_parser->doParse(0);

        $common_parser = new block_formal_langs_parser($grm_parser->list);
        $common_parser->set_start_symbol($grm_parser->start);

        $tokens = $processedstring->tokenstream->tokens;

        $result_ast = $common_parser->parse($tokens, false);
        
        return $result_ast;
    }

    /**
     * Returns true if this language has predefined (hardcoded) lexer and parser.
     *
     * @return boolean
     */
    public function is_predefined() {
        return false;
    }

    /**
     * Returns technical name of the language.
     */
    public function name() {
        return $this->name;
    }

    /**
     * User-visible name of the language
     */
    public function ui_name() {
        return $this->uiname;
    }

    /**
     * User description (help) for the language
     */
    public function description() {
        return '';
    }
}
